Уничтожение объектов и области видимости
=========================================

.. index::
    уничтожение объекта
    область видимости
    деструктор

Мы рассмотрели время жизни объектов в главе "Время жизни и фундаментальные
Операции" (:ref:`Время жизни и фундаментальные операции<глава_44_start>`).
В последующих главах мы видели, что объекты подготавливаются к использованию в
конструкторе, который называется this(); а завершающие операции с объектами
применяются в деструкторе, который называется ~this().
Для структур и других типов значений деструктор выполняется в то время, когда
заканчивается время жизни объекта. Для классов и других ссылочных типов он выполняется
сборщиком мусора некоторое время в будущем. Важное различие заключается в том, что
деструктор объекта класса не выполняется, когда заканчивается его время жизни.
Системные ресурсы обычно возвращаются обратно в систему в деструкторах.
Например, std.stdio.Файл возвращает файловый ресурс обратно операционной
системе в своем деструкторе. Поскольку неизвестно, когда будет вызван деструктор объекта класса
, системные ресурсы, которые он содержит, могут быть возвращены только слишком
поздно, когда другие объекты уже не смогут получить доступ к тому же ресурсу.

Пример позднего вызова деструкторов
-----------------------------------

Давайте определим класс, чтобы увидеть эффект от позднего выполнения деструкторов класса. На сайте
следующий конструктор увеличивает статический счетчик, а деструктор
уменьшает его. Как вы помните, у каждого объекта есть только один статический член, который является общим для всех объектов типа. Такой счетчик будет указывать на количество
объектов, которые еще предстоит уничтожить.

.. code-block:: d

    class LifetimeObserved
    {
        int[] array; // ← Belongs to each object
        static size_t counter; // ← Shared by all objects
        this()
        {
            /* We are using a relatively large array to make each
            * object consume a large amount of memory. Hopefully
            * this will make the garbage collector call object
            * destructors more frequently to open up space for
            * more objects. */
            array.length = 30_000;
            /* Increment the counter for this object that is being
            * constructed. */
            ++counter;
        }

        ~this()
        {
            /* Decrement the counter for this object that is being
                * destroyed. */
            --counter;
        }
    }


Cледующая программа конструирует объекты этого класса внутри цикла:

.. code-block:: d

   import std.stdio;

    void main()
    {
        foreach (i; 0 .. 20)
        {
            auto variable = new LifetimeObserved; // ← start
            write(LifetimeObserved.counter, ' ');
        } // ← end
        writeln();
    }


Время жизни каждого объекта LifetimeObserved на самом деле очень короткое: его жизнь начинается
когда он создается с помощью ключевого слова new и заканчивается в закрывающей фигурной скобке
цикла foreach. После этого каждый объект попадает в зону ответственности сборщика мусора.
Комментарии start и end указывают на начало и конец
времени жизни.
Даже если в данный момент времени жив только один объект, значение
счетчика указывает на то, что деструктор не будет выполнен, когда закончится время жизни:

.. code-block:: console

    1 2 3 4 5 6 7 8 2 3 4 5 6 7 8 2 3 4 5 6

Согласно этому выводу, алгоритм уборки памяти сборщика мусора
отложил выполнение деструктора для 8 объектов.
*(Примечание: вывод может быть
отличаться в зависимости от алгоритма сборки мусора, доступной памяти и других факторов.
факторов.)*

Функция destroy() для выполнения деструктора
--------------------------------------------

Функция destroy() выполняет деструктор для объекта:

.. code-block:: d

    void main()
    {
        foreach (i; 0...20)
        {
            auto variable = new LifetimeObserved;
            write(LifetimeObserved.counter, ' ');
            destroy(variable);
        }
        writeln();
    }


Как и раньше, значение LifetimeObserved.counter увеличивается конструктором
конструктором как результат new и становится равным 1. На этот раз сразу после печати,
destroy() выполняется деструктор объекта, и значение счетчика
снова уменьшается до нуля. Поэтому на этот раз его значение всегда равно 1:

.. code-block:: console

    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

После уничтожения объект должен считаться находящимся в недействительном состоянии и не должен
не должен больше использоваться:

.. code-block:: d

    destroy(variable);
    // ...
    // Предупреждение: Использование потенциально недействительного объекта
    writeln(variable.array);

Хотя функция destroy() предназначена в основном для ссылочных типов, ее можно вызывать и для
структурных объектах, чтобы уничтожить их до окончания обычного срока жизни

Когда использовать
-------------------

Как было показано в предыдущем примере, destroy() используется, когда ресурсы
должны быть освобождены в определенное время, не полагаясь на сборщик мусора

Пример
------
В главе Конструктор и другие специальные функции мы создали структуру XmlElement.
Функции (стр. 279). Эта структура использовалась для печати XML
элементов в формате <tag>значение</tag>. Печать закрывающего тега была
ответственность деструктора:

.. code-block:: d

    struct XmlElement
    {
    // ...
        ~this()
        {
            writeln(indentation, "</", name, '>');
        }
    }

Следующий вывод был получен программой, использующей эту структуру. На этот раз,
я заменяю слово "класс" на "курс", чтобы не путать его с ключевым словом class
ключевым словом:

.. code-block:: xml

    <courses>
        <course0>
            <grade>
                72
            </grade> ← The closing tags appear on correct lines
            <grade>
                97
            </grade> ←
            <grade>
                90
            </grade> ←
        </course0> ←
        <course1>
            <grade>
                77
            </grade> ←
            <grade>
                87
            </grade> ←
            <grade>
                56
            </grade> ←
        </course1> ←
    </courses>

Предыдущий вывод верен, потому что XmlElement - это структура. Желаемый результат достигается просто путем размещения объектов в соответствующих областях видимости

.. code-block:: d

    void main()
    {
        const courses = XmlElement("courses", 0);
        foreach (courseId; 0 .. 2)
        {
            const courseTag = "course" ~ to!string(courseId);
            const courseElement = XmlElement(courseTag, 1);
            foreach (i; 0 .. 3)
            {
                const gradeElement = XmlElement("grade", 2);
                const randomGrade = uniform(50, 101);
                writeln(indentationString(3), randomGrade);
            } // ← gradeElement is destroyed
        } //     ← courseElement is destroyed
    } //        ← courses is destroyed

Деструктор печатает закрывающие теги по мере уничтожения объектов.
Чтобы увидеть, как классы ведут себя по-разному, давайте преобразуем XmlElement в класс:

.. code-block:: d

    import std.stdio;
    import std.array;
    import std.random;
    import std.conv;

    string indentationString(int level)
    {
        return replicate(" ", level * 2);
    }

    class XmlElement
    {
        string name;
        string indentation;
        this(string name, int level)
        {
            this.name = name;
            this.indentation = indentationString(level);
            writeln(indentation, '<', name, '>');
        }

        ~this()
        {
            writeln(indentation, "</", name, '>');
        }
    }

    void main()
    {
        const courses = new XmlElement("courses", 0);
        foreach (courseId; 0 .. 2)
        {
            const courseTag = "course" ~ to!string(courseId);
            const courseElement = new XmlElement(courseTag, 1);
            foreach (i; 0 .. 3)
            {
                const gradeElement = new XmlElement("grade", 2);
                const randomGrade = uniform(50, 101);
                writeln(indentationString(3), randomGrade);
            }
        }
    }


Поскольку ответственность за вызов деструкторов теперь возложена на сборщик мусора
коллектор, программа не выдает желаемого результата:

.. code-block:: xml

    <courses>
        <course0>
            <grade>
                57
            <grade>
                98
            <grade>
                87
        <course1>
            <grade>
                84
            <grade>
                60
            <grade>
                99
            </grade> ← The closing tags appear at the end
            </grade> ←
            </grade> ←
        </course1> ←
            </grade> ←
            </grade> ←
            </grade> ←
        </course0> ←
    </courses>

Деструктор по-прежнему выполняется для каждого объекта, но на этот раз в конце, когда
когда программа завершается. (Примечание: сборщик мусора не гарантирует, что деструктор
будет вызван для каждого объекта. В действительности возможно, что закрывающие теги не будут
напечатаны вообще).
destroy() гарантирует, что деструктор будет вызван в нужные моменты
программе:

.. code-block:: d

    void main()
    {
        const courses = new XmlElement("courses", 0);
        foreach (courseId; 0 .. 2)
        {
            const courseTag = "course" ~ to!string(courseId);
            const courseElement = new XmlElement(courseTag, 1);
            foreach (i; 0 .. 3)
            {
                const gradeElement = new XmlElement("grade", 2);
                const randomGrade = uniform(50, 101);
                writeln(indentationString(3), randomGrade);
                destroy(gradeElement);
            }
            destroy(courseElement);
        }
        destroy(courses);
    }


После этих изменений вывод кода теперь совпадает с выводом кода
в которых используются структуры:

.. code-block:: xml

    <courses>
        <course0>
            <grade>
                66
            </grade> ← The closing tags appear on correct lines
            <grade>
                75
            </grade> ←
            <grade>
                68
            </grade> ←
        </course0> ←
        <course1>
            <grade>
                73
            </grade> ←
            <grade>
                62
            </grade> ←
            <grade>
                100
            </grade> ←
        </course1> ←
    </courses>

.. index::
    scoped

Функция scoped() для автоматического вызова деструктора
-------------------------------------------------------

У приведенной выше программы есть слабое место: Область может выйти из нее до того, как
destroy(), обычно из-за выброшенных исключений. Если строки destroy()
должны выполняться даже при возникновении исключений, решение состоит в том, чтобы воспользоваться
воспользоваться преимуществами scope() и других возможностей, которые мы рассматривали в главе "Исключения
(стр. 192).
Другое решение - конструировать объекты класса с помощью std.typecons.scoped вместо
scoped() оборачивает объект класса внутри struct, а деструктор этого объекта struct уничтожает.
деструктор этого объекта struct уничтожает объект класса, когда сам выходит из области видимости
области видимости.
Эффект от использования scoped() заключается в том, что объекты класса ведут себя аналогично объектам struct
в отношении времени жизни.
При следующих изменениях программа выдает ожидаемый результат, как и
раньше:

.. code-block:: d

    import std.typecons;

    // ...
    void main()
    {
        const courses = scoped!XmlElement("courses", 0);
        foreach (courseId; 0 .. 2)
        {
            const courseTag = "course" ~ to!string(courseId);
            const courseElement = scoped!XmlElement(courseTag, 1);
            foreach (i; 0 .. 3)
            {
                const gradeElement = scoped!XmlElement("grade", 2);
                const randomGrade = uniform(50, 101);
                writeln(indentationString(3), randomGrade);
            }
        }
    }


Обратите внимание, что строки destroy() больше нет.
scoped() - это функция, которая возвращает специальный объект struct, заключающий в себе
реальный объект класса. Возвращаемый объект действует как прокси для инкапсулированного объекта.
(На самом деле, тип вышеуказанных курсов - Scoped, а не XmlElement).
Когда деструктор объекта struct вызывается автоматически по окончании времени его жизни
он вызывает destroy() для объекта класса, который он инкапсулирует. (Это
применение идиомы Resource Acquisition Is Initialization(RAII)). scoped()
достигается с помощью шаблонов (стр. 399) и псевдонима this (стр. 424), которые мы рассмотрим в следующих главах.
которые мы рассмотрим в последующих главах).
Желательно, чтобы прокси-объект использовался как можно удобнее. На самом деле,
объект, который возвращает scoped(), можно использовать точно так же, как и реальный тип класса.
Например, на нем можно вызывать функции-члены реального типа:

.. code-block:: d

    import std.typecons;

    class C
    {
        void foo()
        {
        }
    }

    void main()
    {
        auto p = scoped!C();
        p.foo(); // Proxy object p is being used as type C
    }

Однако за это удобство приходится платить: Прокси-объект может передать
ссылку на реальный объект прямо перед его уничтожением. Это может произойти, если
реальный тип класса указан явно в левой части:

.. code-block:: d

    C c = scoped!C(); // ← ошибка
    c.foo(); // ← попытка доступа к уже уничтоженному объекту

в этом определении c не является прокси-объектом; скорее, как определено программистом,
переменная класса, ссылающаяся на инкапсулированный объект. К сожалению, прокси
построенный в правой части, завершается в конце
выражения, которое его конструирует. В результате использование c в программе будет
ошибка, которая, скорее всего, приведет к ошибке времени выполнения:

.. code-block:: console

    Segmentation fault

По этой причине не следует определять переменные scoped() по фактическому типу:

.. code-block:: d

    C a = scoped!C(); // ← BUG
    auto b = scoped!C(); // ← корректно
    const c = scoped!C(); // ← корректно
    immutable d = scoped!C(); // ← корректно


Резюме
-------
* Функция destroy() предназначена для явного выполнения деструктора объекта класса.
  destroy и scoped
* Объекты, построенные с помощью функции scoped(), уничтожаются при выходе из своей
  соответствующих областей видимости.
* Определение переменных scoped() по фактическому типу является ошибкой.
